package org.yoki.edu.common.protocol.radius.msg.packet;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.yoki.edu.common.protocol.radius.attribute.attr.RadiusMsgAttr;
import org.yoki.edu.common.protocol.radius.attribute.attr.check.UserPasswordAttr;
import org.yoki.edu.common.protocol.radius.exception.RadiusException;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * @author Sky$
 * @Description: TODO
 * @date 2017/12/14$ 16:04$
 */
@ToString
public abstract class RadiusMsg {

    /**
     * Maximum packet length.
     */
    public static final int MAX_PACKET_LENGTH = 4096;

    /**
     * Packet header length.
     */
    public static final int RADIUS_HEADER_LENGTH = 20;


    /**
     * code 属性长度
     */
    public static final int LENGTH_CODE = 1;
    /**
     * code 属性在数组中的下标：0
     */
    public static final int INDEX_CODE = 0;
    /**
     * identifier 属性长度
     */
    public static final int LENGTH_IDENTIFIER = 1;
    /**
     * identifier 属性在数组中的下标：1
     */
    public static final int INDEX_IDENTIFIER = INDEX_CODE + LENGTH_CODE;
    /**
     * length 属性长度
     */
    public static final int LENGTH_LENGTH = 2;
    /**
     * length 属性在数组中的下标：2
     */
    public static final int INDEX_LENGTH = INDEX_IDENTIFIER + LENGTH_IDENTIFIER;
    /**
     * authenticator 属性长度
     */
    public static final int LENGTH_AUTHENTICATOR = 16;
    /**
     * authenticator 属性在数组中的下标：4
     */
    public static final int INDEX_AUTHENTICATOR = INDEX_LENGTH + LENGTH_LENGTH;

    /**
     * attributes 属性在数组中的下标：20
     */
    public static final int INDEX_ATTRIBUTES = INDEX_AUTHENTICATOR + LENGTH_AUTHENTICATOR;

    /**
     * 1 bytes
     */
    @Getter
    @Setter
    public int code;

    /**
     * 1 bytes
     */
    @Getter
    @Setter
    public int identifier;
    /**
     * 2 bytes
     */
    @Getter
    @Setter
    public int length;
    /**
     * 16 bytes
     */
    @Getter
    @Setter
    public byte[] authenticator;

    @Getter
    @Setter
    public List<RadiusMsgAttr> attributes = new ArrayList<>();

    @Getter
    @Setter
    public String secret;

    public RadiusMsg() {
        code = getPacketCode();
    }

    public RadiusMsg(String secret) {
        this.secret = secret;
        code = getPacketCode();
    }

    public void setSecret(String secret) {
        this.secret = secret;
        if (null != attributes) {
            for (RadiusMsgAttr attr : attributes) {
                if (attr.getAttrType() == UserPasswordAttr.ATTR_TYPE) {
                    ((UserPasswordAttr) attr).setSecret(secret);
                    break;
                }
            }
        }
    }

    /**
     * 获取数据包code<br></br>
     * <span color=red><b>子类进行复写此方法</b></span>
     *
     * @return
     */
    public abstract int getPacketCode();


    protected void parseHeader(byte[] input) {
        this.code = 0xff & input[INDEX_CODE];
        this.identifier = 0xff & input[INDEX_IDENTIFIER];
        this.length = ((0xff & input[INDEX_LENGTH]) << 8) | (0xff & input[INDEX_LENGTH + 1]);
    }

    protected void parseAuthenticator(byte[] input) {
        this.authenticator = input;
    }

    protected abstract void parseAttributes(List<byte[]> attrBytesList) throws RadiusException;

    protected void parseAttributes(byte[] input) throws RadiusException {
        //判断属性长度是否合法
        if (null == input && input.length < (RadiusMsgAttr.LENGTH_TYPE + RadiusMsgAttr.LENGTH_LENGTH)) {
            return;
        }
        int index = 0;
        //将字节数组分割成每个属性的字节数组
        List<byte[]> attrBytesList = new LinkedList<>();
        while (index < input.length) {
            int length = input[index + RadiusMsgAttr.INDEX_LENGTH];
            byte[] attrBytes = new byte[length];
            System.arraycopy(input, index, attrBytes, 0, length);
            attrBytesList.add(attrBytes);
            index = index + length;
        }
        parseAttributes(attrBytesList);
    }

    public void parse(byte[] input) throws RadiusException {
        //判断字节数组长度是否合法
        if (null == input) {
            throw new NullPointerException();
        } else if (input.length < (LENGTH_CODE + LENGTH_IDENTIFIER + LENGTH_LENGTH + LENGTH_AUTHENTICATOR)) {
            throw new IndexOutOfBoundsException();
        }
        int rowLength = ((0xff & input[INDEX_LENGTH]) << 8) | (0xff & input[INDEX_LENGTH + 1]);
        if (rowLength > input.length) {
            throw new IndexOutOfBoundsException();
        } else if (rowLength < (LENGTH_CODE + LENGTH_IDENTIFIER + LENGTH_LENGTH + LENGTH_AUTHENTICATOR)) {
            throw new NegativeArraySizeException();
        }
        //判断code是否合法
        int code = input[INDEX_CODE];
        if (getPacketCode() != code) {
            throw new IllegalArgumentException("不能将数据转化为此类型的RadiusMsg，Code不一致 !!! " +
                    "当前要求的code为 [" + getPacketCode() + "]，传入的code为 [" + code + "]");
        }
        //重置所有属性
        rest();
        //解析头部
        byte[] headerBytes = new byte[LENGTH_CODE + LENGTH_IDENTIFIER + LENGTH_LENGTH];
        System.arraycopy(input, 0, headerBytes, 0, headerBytes.length);
        parseHeader(headerBytes);
        //解析密文
        byte[] authenticatorBytes = new byte[LENGTH_AUTHENTICATOR];
        System.arraycopy(input, INDEX_AUTHENTICATOR, authenticatorBytes, 0, authenticatorBytes.length);
        parseAuthenticator(authenticatorBytes);
        //解析属性集合
        byte[] attrListBytes = new byte[this.getLength() - (LENGTH_CODE + LENGTH_IDENTIFIER + LENGTH_LENGTH + LENGTH_AUTHENTICATOR)];
        System.arraycopy(input, (LENGTH_CODE + LENGTH_IDENTIFIER + LENGTH_LENGTH + LENGTH_AUTHENTICATOR), attrListBytes, 0, attrListBytes.length);
        parseAttributes(attrListBytes);
        //重新设置长度，应为在parseAttributes的时候会将无效的参数去除
        length = LENGTH_CODE + LENGTH_IDENTIFIER + LENGTH_LENGTH + LENGTH_AUTHENTICATOR;
        for (RadiusMsgAttr attr : attributes) {
            length += attr.getLength();
        }
    }

    /*public void parse(int code, int identifier, int length, byte[] authenticator, List<RadiusMsgAttr> attributes){
        if (code > (Byte.MAX_VALUE - Byte.MIN_VALUE)
                || (identifier > (Byte.MAX_VALUE - Byte.MIN_VALUE))) {
            throw new IndexOutOfBoundsException();
        } else if (null == authenticator) {
            throw new NullPointerException();
        } else if (authenticator.length > LENGTH_AUTHENTICATOR) {
            throw new IndexOutOfBoundsException();
        }
        int attrsLength = 0;
        if (null != attributes && attributes.size() > 0) {
            for (RadiusMsgAttr radiusMsgAttr : attributes) {
                attrsLength += radiusMsgAttr.getLength();
            }
        }
        if (length != (LENGTH_CODE + LENGTH_IDENTIFIER + LENGTH_LENGTH + LENGTH_AUTHENTICATOR + attrsLength)) {
            throw new IndexOutOfBoundsException();
        }
        if (getPacketCode() != code) {
            throw new IllegalArgumentException("不能将数据转化为此类型的RadiusMsg，Code不一致 !!! " +
                    "当前要求的code为 [" + getPacketCode() + "]，传入的code为 [" + code + "]");
        }
        //重置所有属性
        rest();
        this.code = code;
        this.identifier = identifier;
        this.length = length;
        this.authenticator = authenticator;
        this.attributes = attributes;
    }*/


    protected byte[] getHanderBytes() {
        byte[] out = new byte[LENGTH_CODE + LENGTH_IDENTIFIER + LENGTH_LENGTH];
        out[INDEX_CODE] = (byte) (this.code & 0xff);
        out[INDEX_IDENTIFIER] = (byte) (this.identifier & 0xff);
        out[INDEX_LENGTH] = (byte) (this.length >> 8 & 0xff);
        out[INDEX_LENGTH + 1] = (byte) (this.length & 0xff);
        return out;
    }

    /**
     * 加密
     *
     * @param byteWithoutAuthenticator 除了Authenticator的字节码
     * @return
     */
    protected abstract byte[] getAuthenticatorBytes(byte[] byteWithoutAuthenticator);


    protected abstract List<byte[]> getAttributesBytesList();

    private byte[] getAttributesBytes() {
        int attrByteNum = 0;
        List<byte[]> bytesList = getAttributesBytesList();
        if (bytesList != null && !bytesList.isEmpty()) {
            for (byte[] attrBytes : bytesList) {
                attrByteNum += attrBytes.length;
            }
        }
        byte[] out = new byte[attrByteNum];
        int index = 0;
        if (bytesList != null && !bytesList.isEmpty()) {
            for (byte[] attrBytes : bytesList) {
                System.arraycopy(attrBytes, 0, out, index, attrBytes.length);
                index += attrBytes.length;
            }
        }
        return out;
    }

    public byte[] getBytes() {
        byte[] byteWithoutAuthenticator = getBytesWithoutAuthenticator();
        byte[] auth = getAuthenticatorBytes(byteWithoutAuthenticator);
        System.arraycopy(auth, 0, byteWithoutAuthenticator, INDEX_AUTHENTICATOR, LENGTH_AUTHENTICATOR);
        return byteWithoutAuthenticator;
    }

    public byte[] getBytes(String newSecret) {
        String oldSecret = secret;
        secret = newSecret;
        byte[] bytes = getBytes();
        secret = oldSecret;
        return bytes;
    }

    protected byte[] getBytesWithoutAuthenticator() {
        byte[] headerBytes = getHanderBytes();
        byte[] attributesBytes = getAttributesBytes();
        byte[] out = new byte[headerBytes.length + LENGTH_AUTHENTICATOR + attributesBytes.length];
        //重新设置长度
//        headerBytes[INDEX_LENGTH] = (byte) (out.length >> 8 & 0xFF);
//        headerBytes[INDEX_LENGTH + 1] = (byte) (out.length & 0xFF);
        System.arraycopy(headerBytes, 0, out, 0, headerBytes.length);
        System.arraycopy(attributesBytes, 0, out, INDEX_ATTRIBUTES, attributesBytes.length);
        return out;
    }


    protected void rest() {
        this.code = 0;
        this.length = 0;
        this.identifier = 0;
        this.authenticator = null;
        this.attributes = new LinkedList<>();
    }

    /**
     * 检验属性类别是否合法
     *
     * @param type true合法，false非法
     * @return
     */
    protected abstract boolean typeLegal(int type, boolean print);

}
